package guid

import (
	"fmt"
	"sync"
	"time"
)

const (
	WorkerIdBits = 10 // Num of WorkerId Bits
	SequenceBits = 12 // Num of Sequence Bits

	WorkerIdShift  = 12
	TimeStampShift = 22
)

var (
	projectId        = 0
	MaxWorker        = maxInt(WorkerIdBits)
	SequenceMask     = maxInt(SequenceBits)
	projectStartTime = time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local)
	projectStartMs   = projectStartTime.UnixNano() / 1000000
)

func SetProjectStartTime(t time.Time) {
	projectStartTime = t
	projectStartMs = projectStartTime.UnixNano() / 1000000
}
func GetProjectStartTime() (t time.Time) {
	return projectStartTime
}

// IdWorker Struct
type IdWorker struct {
	workerId      int64
	lastTimeStamp int64
	sequence      int64
	lock          sync.Mutex
}

func NewWorker() (iw *IdWorker) {
	iw, _ = NewIdWorker(int64(projectId))
	return
}

// NewIdWorker Func: Generate NewIdWorker with Given workerid
func NewIdWorker(workerId int64) (iw *IdWorker, err error) {
	if workerId > MaxWorker || workerId < 0 {
		err = fmt.Errorf("workerId must between [0, %d], but is [%d]", MaxWorker, workerId)
		return
	}

	iw = &IdWorker{}

	iw.workerId = workerId
	iw.lastTimeStamp = -1
	iw.sequence = 0
	return iw, nil
}

// return in ms
func (iw *IdWorker) timeGen() int64 {
	return time.Now().UnixNano() / 1000 / 1000
}

func (iw *IdWorker) timeReGen() int64 {
	nextTs := iw.lastTimeStamp + 1
	waitTime := time.Unix(0, nextTs*1000*1000)

	<-time.After(time.Until(waitTime))
	return iw.timeGen()
}

// Generate next id
func (iw *IdWorker) NextId() (ts int64, err error) {
	iw.lock.Lock()
	defer iw.lock.Unlock()
	ts = iw.timeGen()
	if ts == iw.lastTimeStamp {
		iw.sequence = (iw.sequence + 1) & SequenceMask
		if iw.sequence == 0 {
			ts = iw.timeReGen()
		}
	} else {
		iw.sequence = 0
	}

	if ts < iw.lastTimeStamp {
		err = fmt.Errorf("clock moved backwards, refuse gen id")
		return
	}
	iw.lastTimeStamp = ts
	ts = (ts-projectStartMs)<<TimeStampShift | iw.workerId<<WorkerIdShift | iw.sequence
	return ts, nil
}
func GenId(t time.Time, ts int64, workerId int64, seq int64) (id int64) {
	ts = t.UnixNano() / 1000 / 1000
	id = (ts-projectStartMs)<<TimeStampShift | workerId<<WorkerIdShift | seq
	return
}
func ParseId(id int64) (t time.Time, ts int64, workerId int64, seq int64) {
	seq = id & SequenceMask
	workerId = (id >> WorkerIdShift) & MaxWorker
	ts = (id >> TimeStampShift) + projectStartMs
	t = time.Unix(ts/1000, (ts%1000)*1000000)
	return
}
func maxInt(bit int) int64 {
	return int64(-1 ^ -1<<bit)
}
